package zack.project.infrastructure.persistent.repository;

import zack.project.domain.award.model.valobj.AccountStatusVO;
import zack.project.domain.credit.adapter.repository.ICreditRepository;
import zack.project.domain.credit.model.aggregate.TradeAggregate;
import zack.project.domain.credit.model.entity.CreditAccountEntity;
import zack.project.domain.credit.model.entity.CreditOrderEntity;
import zack.project.domain.credit.model.entity.TaskEntity;
import zack.project.infrastructure.event.EventPublisher;
import zack.project.infrastructure.persistent.dao.ITaskDao;
import zack.project.infrastructure.persistent.dao.IUserCreditAccountDao;
import zack.project.infrastructure.persistent.dao.IUserCreditOrderDao;
import zack.project.infrastructure.persistent.po.Task;
import zack.project.infrastructure.persistent.po.UserCreditAccount;
import zack.project.infrastructure.persistent.po.UserCreditOrder;
import zack.project.infrastructure.persistent.redis.IRedisService;
import cn.bugstack.middleware.db.router.strategy.IDBRouterStrategy;
import zack.project.types.common.Constants;
import zack.project.types.enums.ResponseCode;
import zack.project.types.exception.AppException;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;

@Repository
@Slf4j
public class CreditRepository implements ICreditRepository {

    @Resource
    private IUserCreditAccountDao userCreditAccountDao;
    @Resource
    private IUserCreditOrderDao userCreditOrderDao;
    @Resource
    private ITaskDao taskDao;
    @Resource
    private IRedisService redisService;
    @Resource
    private TransactionTemplate transactionTemplate;
    @Resource
    private IDBRouterStrategy dbRouter;
    @Resource
    private EventPublisher eventPublisher;

    @Override
    public void saveUserCreditTradeOrder(TradeAggregate tradeAggregate) {
        //构建积分订单po对象,插入数据库表{user_credit_order}
        CreditOrderEntity orderEntity = tradeAggregate.getOrderEntity();
        UserCreditOrder userCreditOrderReq = new UserCreditOrder();
        userCreditOrderReq.setUserId(orderEntity.getUserId());
        userCreditOrderReq.setOrderId(orderEntity.getOrderId());
        userCreditOrderReq.setTradeAmount(orderEntity.getTradeAmount());
        userCreditOrderReq.setTradeType(orderEntity.getTradeType().getCode());
        userCreditOrderReq.setTradeName(orderEntity.getTradeName().getName());
        userCreditOrderReq.setOutBusinessNo(orderEntity.getOutBusinessNo());
        //构建积分账户po对象,修改数据库表{user_credit_account},
        // 因为后续查询用户积分账户时,如果该该用户没有对用的积分账户时需要插入一个新的账户,
        // 故此时需要将totalAmount也设置上,防止入库的新账户出现字段不全的情况
        CreditAccountEntity accountEntity = tradeAggregate.getAccountEntity();
        UserCreditAccount userCreditAccountReq = new UserCreditAccount();
        userCreditAccountReq.setUserId(accountEntity.getUserId());
        userCreditAccountReq.setAvailableAmount(accountEntity.getAdjustAmount());
        userCreditAccountReq.setTotalAmount(accountEntity.getAdjustAmount());
        userCreditAccountReq.setAccountStatus(AccountStatusVO.open.getCode());
        //构建积分调整补偿任务
        TaskEntity taskEntity = tradeAggregate.getTaskEntity();
        Task task = new Task();
        task.setUserId(taskEntity.getUserId());
        task.setTopic(taskEntity.getTopic());
        task.setMessageId(taskEntity.getMessageId());
        task.setMessage(JSON.toJSONString(taskEntity.getMessage()));
        task.setState(taskEntity.getState().getCode());

        String userId = tradeAggregate.getUserId();
        //在修改数据库表 {user_credit_account}用户的积分账户之前,
        // 在缓存中获取一个锁:cacheKey(user_credit_account_lock_#{userId}_#{ouBusinessNo})
        RLock lock = redisService.getLock(Constants.RedisKey.USER_CREDIT_ACCOUNT_LOCK +
                userId +
                Constants.UNDERLINE +
                orderEntity.getOutBusinessNo());

        try {
            lock.lock(3, TimeUnit.SECONDS);
            dbRouter.doRouter(userId);
            transactionTemplate.execute(status -> {
                try {
                    //根据po对象的userId查询数据库表{user_credit_account}中对应的用户积分账户
                    UserCreditAccount userCreditAccount =
                            userCreditAccountDao.queryUserCreditAccount(userCreditAccountReq);
                    //如果没有积分账户则根据插入一个新的账户
                    if (userCreditAccount == null) {
                        userCreditAccountDao.insert(userCreditAccountReq);
                    } else {
                        //存在新的账户则在原来的账户基础上更新积分值
                        BigDecimal availableAmount = userCreditAccountReq.getAvailableAmount();
                        //根据积分变更量是正或负来判断是走add还是subtraction
                        if(availableAmount.compareTo(BigDecimal.ZERO) > 0){
                            userCreditAccountDao.updateAddAmount(userCreditAccountReq);
                        }else{
                          int subtractionCount =
                                  userCreditAccountDao.updateSubtractionAmount(userCreditAccountReq);
                            if (1 != subtractionCount) {
                                status.setRollbackOnly();
                                throw new AppException(ResponseCode.USER_CREDIT_ACCOUNT_NO_AVAILABLE_AMOUNT.getCode(), ResponseCode.USER_CREDIT_ACCOUNT_NO_AVAILABLE_AMOUNT.getInfo());
                            }
                        }
                    }
                    //在更新完用户积分账户之后再插入新的积分订单
                    userCreditOrderDao.insert(userCreditOrderReq);
                    //插入积分调整的补偿任务
                    taskDao.insert(task);

                } catch (DuplicateKeyException e) {
                    status.setRollbackOnly();
                    log.error("调整账户积分额度异常,唯一索引冲突 userId:{} orderId:{}", userId, orderEntity.getOrderId(), e);

                } catch (Exception e) {
                    status.setRollbackOnly();
                    log.error("调整账户积分额度失败 userId:{} orderId:{}", userId, orderEntity.getOrderId(), e);
                }
                return 1;
            });

        } finally {
            dbRouter.clear();
            if(lock.isLocked()){
                lock.unlock();
            }

        }
        //在调整完用户的积分账户的积分,插入积分订单和补偿任务后,向CreditAdjustSuccessCustomer监听的消息队列投放一个积分调整成功的消息,
        //CreditAdjustSuccessCustomer监听并消费BaseEvent.EventMessage<CreditAdjustSuccessMessageEvent.CreditAdjustSuccessMessage>消息
        //根据CreditAdjustSuccessMessage消息内容更新对应的数据库表{raffle_activity_order}充值单的状态为completed
        //并更新数据库表{raffle_activity_account},{raffle_activity_account_day},{raffle_activity_account_month}中对应的抽奖次数,完成充值
        try {
            eventPublisher.publish(task.getTopic(), task.getMessage());
            //如果积分调整消息被成功消息则将补偿任务设为completed
            taskDao.updateTaskSendMessageCompleted(task);
            log.info("调整账户积分记录,发送MQ消息完成 userId: {} orderId:{} topic: {}", userId, orderEntity.getOrderId(), task.getTopic());
        } catch (Exception e) {
            log.error("调整账户积分记录,发送MQ消息失败 userId: {} topic: {}", userId, task.getTopic());
            taskDao.updateTaskSendMessageFail(task);
        }


    }

    @Override
    public CreditAccountEntity queryUserCreditAccount(String userId) {
        UserCreditAccount userCreditAccountReq = new UserCreditAccount();
        userCreditAccountReq.setUserId(userId);

        try {
            dbRouter.doRouter(userId);
            UserCreditAccount userCreditAccount = userCreditAccountDao.queryUserCreditAccount(userCreditAccountReq);
            BigDecimal availableAmount = BigDecimal.ZERO;
            if (null != userCreditAccount) {
                availableAmount = userCreditAccount.getAvailableAmount();
            }
            return CreditAccountEntity.builder().userId(userId).adjustAmount(availableAmount).build();
        } finally {
            dbRouter.clear();
        }
    }
}
